﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace EaWebService
{

    /// <summary>
    /// Class for population - representation UnitInt
    /// </summary>
    class Population : ISelection<UnitInt>, ICrossover<UnitInt>, IFitnessScale
    {
        private int size;
        public int Size
        {
            get { return size; }
            set { size = value; }
        }
        private List<UnitInt> population;
        internal List<UnitInt> PopulationList
        {
            get { return population; }
            set { population = value; }
        }

        // For sigma fitness scaling
        private double sigma = 1;
        public double Sigma
        {
            get { return sigma; }
            set { sigma = value; }
        }
        // For Boltzmann fitness scaling
        private double boltzmannTemp;
        public double BoltzmannTemp
        {
            get { return boltzmannTemp; }
            set { boltzmannTemp = value; }
        }

        // For tracking statistical fitness values
        private static double averageFitness=0;
        private static double bestFitness=0;
        private static double worstFitness=0;
        private static double totalFitness=0;



        public Population(int size, List<UnitInt> population)
        {
            this.size = size;
            this.population = population;
        }

        public Population()
        {
            population = new List<UnitInt>();
            size = 0;
        }

        public Population(int size)
        {
            population = new List<UnitInt>();
            this.size = size;
        }



        //IFitnessScale interface
        public void FitnessScaleRank()
        {
            //Scale by position
            population.Sort(UnitInt.Compare);
            for (int i = 0; i < population.Count; i++)
            {
                population[i].FitnessValue = i;
            }
        }

        public void FitnessScaleSigma()
        {
            //Scale using standard deviation.
            if (totalFitness == 0)
            {
                CalculateBestWorstAverageTotalFitness();
            }
            double sum = 0;
            for (int i = 0; i < population.Count; i++)
            {
                sum += (population[i].FitnessValue - averageFitness) *
                                (population[i].FitnessValue - averageFitness);
            }
            double variance = sum / (double)population.Count;
            //standard deviation
            sigma = Math.Sqrt(variance);

            for (int i = 0; i < population.Count; i++)
            {
                double oldFitness = population[i].FitnessValue;
                population[i].FitnessValue = (oldFitness - averageFitness) / (2 * sigma);
            }

            CalculateBestWorstAverageTotalFitness();
        }

        public void FitnessScaleBoltzmann()
        {
            //Scale using Boltzmann temperature.
            boltzmannTemp -= 0.5;
            if (boltzmannTemp < 1)
                boltzmannTemp = 1;

            ArrayList expBoltz = new ArrayList();
            double average = 0.0;
            for (int i = 0; i < population.Count; i++)
            {
                expBoltz.Add(Math.Exp(population[i].FitnessValue / boltzmannTemp));
                average = average + (double)expBoltz[i];
            }
            average /= (double)population.Count;
            for (int i = 0; i < population.Count; i++)
            {
                population[i].FitnessValue = (double)expBoltz[i] / average;
            }
        }



        // Recalculate statistical fitness values
        internal void CalculateBestWorstAverageTotalFitness()
        {
            bestFitness = population[0].FitnessValue;
            worstFitness = population[0].FitnessValue;
            totalFitness = 0;
            averageFitness = 0;

            for (int i = 0; i < population.Count; i++)
            {
                if (population[i].FitnessValue > bestFitness)
                {
                    bestFitness = population[i].FitnessValue;
                }
                if (population[i].FitnessValue < worstFitness)
                {
                    worstFitness = population[i].FitnessValue;
                }
                totalFitness += population[i].FitnessValue;
            }
            averageFitness = totalFitness / population.Count;
        }


        // Reset statistical fitness values
        internal void ResetBestWorstAverageTotalFitness()
        {
            bestFitness = 0;
            worstFitness = 0;
            totalFitness = 0;
            averageFitness = 0;
        }


        //ISelection interface
        public UnitInt SelectionRouletteWheel()
        {
            Random rand = new Random();
            if (totalFitness == 0)
                CalculateBestWorstAverageTotalFitness();
            double fSlice = rand.NextDouble() * totalFitness;
            double cfTotal = 0.0;
            int selectedGenome = 0;
            for (int i = 0; i < population.Count; i++)
            {
                cfTotal += population[i].FitnessValue;
                if (cfTotal > fSlice)
                {
                    selectedGenome = i;
                    break;
                }
            }
            return population[selectedGenome];
        }


        public UnitInt SelectionTournament()
        {
            Random rand = new Random();
            double bestFitnessSoFar = 0;
            int chosenOne = 0;
            int n = (int)(population.Count * 0.2);
            for (int i = 0; i < n; i++)
            {
                int thisTry = rand.Next(population.Count);
                if (population[i].FitnessValue > bestFitnessSoFar)
                {
                    chosenOne = thisTry;
                    bestFitnessSoFar = population[i].FitnessValue;
                }
            }
            return population[chosenOne];
        }


        public UnitInt SelectionAlternateTournament()
        {
            Random rand = new Random();
            int index1 = rand.Next(population.Count);
            int index2 = rand.Next(population.Count);
            while (index1 == index2)
            {
                index2 = rand.Next(population.Count);
            }

            double probability = 0.75;
            if (rand.NextDouble() < probability)
            {
                if (population[index1].FitnessValue > population[index2].FitnessValue)
                {
                    return population[index1];
                }
                else
                {
                    return population[index2];
                }
            }
            else
            {
                if (population[index1].FitnessValue > population[index2].FitnessValue)
                {
                    return population[index2];
                }
                else
                {
                    return population[index1];
                }
            }
        }


        //ICrossover
        public void CrossoverOnePoint(UnitInt father, UnitInt mother, List<UnitInt> newpopulation)
        {
            Random rand = new Random();
            int[] fatherCode = father.GenCode;
            int[] motherCode = mother.GenCode;
            int point = rand.Next(fatherCode.Length);
            int[] babby1Code = new int[fatherCode.Length];
            int[] babby2Code = new int[fatherCode.Length];

            for (int i = 0; i < point; i++)
            {
                babby1Code[i] = fatherCode[i];
                babby2Code[i] = motherCode[i];
            }

            for (int i = point; i < fatherCode.Length; i++)
            {
                babby2Code[i] = fatherCode[i];
                babby1Code[i] = motherCode[i];
            }

            UnitInt babby1 = new UnitInt(babby1Code.Length, babby1Code);
            UnitInt babby2 = new UnitInt(babby2Code.Length, babby2Code);

            newpopulation.Add(babby1);
            newpopulation.Add(babby2);
        }


        public void CrossoverTwoPoints(UnitInt father, UnitInt mother, List<UnitInt> newpopulation)
        {
            Random rand = new Random();
            int[] fatherCode = father.GenCode;
            int[] motherCode = mother.GenCode;
            int point2 = rand.Next(fatherCode.Length);
            while (point2 < fatherCode.Length / 3)
                point2 = rand.Next(fatherCode.Length);
            int point1 = rand.Next(point2);
            int[] babby1Code = new int[fatherCode.Length];
            int[] babby2Code = new int[fatherCode.Length];

            for (int i = 0; i < point1; i++)
            {
                babby1Code[i] = fatherCode[i];
                babby2Code[i] = motherCode[i];
            }

            for (int i = point1; i < point2; i++)
            {
                babby2Code[i] = fatherCode[i];
                babby1Code[i] = motherCode[i];
            }

            for (int i = point2; i < fatherCode.Length; i++)
            {
                babby1Code[i] = fatherCode[i];
                babby2Code[i] = motherCode[i];
            }

            UnitInt babby1 = new UnitInt(babby1Code.Length, babby1Code);
            UnitInt babby2 = new UnitInt(babby2Code.Length, babby2Code);

            newpopulation.Add(babby1);
            newpopulation.Add(babby2);
        }


        public void CrossoverUniform(UnitInt father, UnitInt mother, double probability, List<UnitInt> newpopulation)
        {
            Random rand = new Random();
            int[] fatherCode = father.GenCode;
            int[] motherCode = mother.GenCode;
            int[] babby1Code = new int[fatherCode.Length];
            int[] babby2Code = new int[fatherCode.Length];
            for (int i = 0; i < fatherCode.Length; i++)
            {
                if (rand.NextDouble() > probability)
                {
                    babby1Code[i] = fatherCode[i];
                    babby2Code[i] = motherCode[i];
                }
                else
                {
                    babby2Code[i] = fatherCode[i];
                    babby1Code[i] = motherCode[i];
                }
            }

            UnitInt babby1 = new UnitInt(babby1Code.Length, babby1Code);
            UnitInt babby2 = new UnitInt(babby2Code.Length, babby2Code);

            newpopulation.Add(babby1);
            newpopulation.Add(babby2);
        }


        public void CrossoverHalfUniform(UnitInt father, UnitInt mother, double probability, List<UnitInt> newpopulation)
        {
            Random rand = new Random();
            int[] fatherCode = father.GenCode;
            int[] motherCode = mother.GenCode;
            int[] babby1Code = new int[fatherCode.Length];
            int[] babby2Code = new int[fatherCode.Length];
            for (int i = 0; i < fatherCode.Length; i++)
            {
                if (fatherCode[i] == motherCode[i])
                {
                    babby1Code[i] = fatherCode[i];
                    babby2Code[i] = motherCode[i];
                }
                else
                {
                    if (rand.NextDouble() > probability)
                    {
                        babby1Code[i] = fatherCode[i];
                        babby2Code[i] = motherCode[i];
                    }
                    else
                    {
                        babby2Code[i] = fatherCode[i];
                        babby1Code[i] = motherCode[i];
                    }
                }
            }

            UnitInt babby1 = new UnitInt(babby1Code.Length, babby1Code);
            UnitInt babby2 = new UnitInt(babby2Code.Length, babby2Code);

            newpopulation.Add(babby1);
            newpopulation.Add(babby2);
        }

    }

}
